Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET object access..
The main program:
// UAConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server // using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET // object access. // // Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . // OPC client and subscriber examples in C# on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-CSharp . // Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own // a commercial license in order to use Online Forums, and we reply to every post. using System; using System.Diagnostics; using System.Threading; using OpcLabs.EasyOpc.UA; using OpcLabs.EasyOpc.UA.LiveMapping; using OpcLabs.EasyOpc.UA.LiveMapping.Extensions; using OpcLabs.EasyOpc.UA.Navigation; using OpcLabs.EasyOpc.UA.OperationModel; namespace UAConsoleLiveMapping { class Program { static void Main() { // the OPC server UAEndpointDescriptor endpointDescriptor = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"; // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported) // or "https://opcua.demo-this.com:51212/UA/SampleServer/" Console.WriteLine(); Console.WriteLine("Mapping our data structures to OPC..."); var mapper = new UAClientMapper(); var boiler1 = new Boiler(); mapper.Map(boiler1, new UAMappingContext { EndpointDescriptor = endpointDescriptor, // The NodeDescriptor below determines where in the OPC address space we want to map our data to. NodeDescriptor = new UANodeDescriptor { // '#' is a reserved character in a browse name, and must be escaped by '&' in the path below. BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler ", "http://opcfoundation.org/UA/Boiler/") }, MonitoringParameters = 1000, // requested sampling interval (for subscriptions) }); Console.WriteLine(); Console.WriteLine("Starting the simulation of the boiler in the server, using an OPC method call..."); // Currently there is no live mapping for OPC methods, therefore we call the OPC method in a traditional way. try { EasyUAClient.SharedInstance.CallMethod( endpointDescriptor, UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler /Simulation", "http://opcfoundation.org/UA/Boiler/"), UABrowsePath.Parse("[nsu=http://opcfoundation.org/UA/Boiler/;i=1287].Start", "http://opcfoundation.org/UA/")); } catch (UAException) { // Production code would test the current state of the simulation first, and also handle the exception here. } Console.WriteLine(); Console.WriteLine("Reading all data of the boiler..."); mapper.Read(); Console.WriteLine($"Drum level is: {boiler1.Drum.LevelIndicator.Output}"); Console.WriteLine(); Console.WriteLine("Writing new setpoint value..."); boiler1.LevelController.SetPoint = 50.0; Debug.Assert(!(boiler1.LevelController is null)); mapper.WriteTarget(boiler1.LevelController, /*recurse:*/false); Console.WriteLine(); Console.WriteLine("Subscribing to boiler data changes..."); mapper.Subscribe(/*active:*/true); Thread.Sleep(30 * 1000); Console.WriteLine(); Console.WriteLine("Unsubscribing from boiler data changes..."); mapper.Subscribe(/*active:*/false); Console.WriteLine(); Console.WriteLine("Press Enter to continue..."); Console.ReadLine(); } } }
' UAConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server ' using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET ' object access. ' ' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . ' OPC client and subscriber examples in VB.NET on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-VBNET . ' Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own ' a commercial license in order to use Online Forums, and we reply to every post. Imports System.Threading Imports OpcLabs.EasyOpc.UA Imports OpcLabs.EasyOpc.UA.LiveMapping Imports OpcLabs.EasyOpc.UA.LiveMapping.Extensions Imports OpcLabs.EasyOpc.UA.Navigation Imports OpcLabs.EasyOpc.UA.OperationModel Friend Class Program Shared Sub Main() ' Define which server we will work with. Dim endpointDescriptor As UAEndpointDescriptor = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer" ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported) ' or "https://opcua.demo-this.com:51212/UA/SampleServer/" Console.WriteLine() Console.WriteLine("Mapping our data structures to OPC...") Dim mapper = New UAClientMapper() Dim boiler1 = New Boiler() ' The NodeDescriptor below determines where in the OPC address space we want to map our data to. ' '#' is a reserved character in a browse name, and must be escaped by '&' in the path below. mapper.Map(boiler1, New UAMappingContext With { .EndpointDescriptor = endpointDescriptor, .NodeDescriptor = New UANodeDescriptor With { .BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler ", "http://opcfoundation.org/UA/Boiler/")}, .MonitoringParameters = 1000}) ' requested sampling interval (for subscriptions) - local OPC server Console.WriteLine() Console.WriteLine("Starting the simulation of the boiler in the server, using an OPC method call...") ' Currently there is no live mapping for OPC methods, therefore we call the OPC method in a traditional way. Try EasyUAClient.SharedInstance.CallMethod( endpointDescriptor, UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler /Simulation", "http://opcfoundation.org/UA/Boiler/"), UABrowsePath.Parse("[nsu=http://opcfoundation.org/UA/Boiler/;i=1287].Start", "http://opcfoundation.org/UA/")) Catch e1 As UAException ' Production code would test the current state of the simulation first, and also handle the exception here. End Try Console.WriteLine() Console.WriteLine("Reading all data of the boiler...") mapper.Read() Console.WriteLine("Drum level is: {0}", boiler1.Drum.LevelIndicator.Output) Console.WriteLine() Console.WriteLine("Writing new setpoint value...") boiler1.LevelController.SetPoint = 50.0 Debug.Assert(boiler1.LevelController IsNot Nothing) mapper.WriteTarget(boiler1.LevelController, False) 'recurse: Console.WriteLine() Console.WriteLine("Subscribing to boiler data changes...") mapper.Subscribe(True) 'active: Thread.Sleep(30 * 1000) Console.WriteLine() Console.WriteLine("Unsubscribing from boiler data changes...") mapper.Subscribe(False) 'active: Console.WriteLine() Console.WriteLine("Press Enter to continue...") Console.ReadLine() End Sub End Class
The Boiler class:
// // Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . // OPC client and subscriber examples in C# on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-CSharp . // Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own // a commercial license in order to use Online Forums, and we reply to every post. using System; using OpcLabs.BaseLib.LiveMapping; using OpcLabs.EasyOpc.UA; using OpcLabs.EasyOpc.UA.LiveMapping; namespace UAConsoleLiveMapping { // The Boiler and its constituents are described in our application domain terms, the way we want to work with them. // Attributes are used to describe the correspondence between our types and members, and OPC nodes. // This is how the boiler looks in OPC address space: // - Boiler #1 // - CC1001 (CustomController) // - ControlOut // - Description // - Input1 // - Input2 // - Input3 // - Drum1001 (BoilerDrum) // - LIX001 (LevelIndicator) // - Output // - FC1001 (FlowController) // - ControlOut // - Measurement // - SetPoint // - LC1001 (LevelController) // - ControlOut // - Measurement // - SetPoint // - Pipe1001 (BoilerInputPipe) // - FTX001 (FlowTransmitter) // - Output // - Pipe1002 (BoilerOutputPipe) // - FTX002 (FlowTransmitter) // - Output [UANamespace("http://opcfoundation.org/UA/Boiler/")] [UAType] class Boiler { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/PipeX001")] public BoilerInputPipe InputPipe = new BoilerInputPipe(); [UANode(BrowsePath = "/DrumX001")] public BoilerDrum Drum = new BoilerDrum(); [UANode(BrowsePath = "/PipeX002")] public BoilerOutputPipe OutputPipe = new BoilerOutputPipe(); [UANode(BrowsePath = "/FCX001")] public FlowController FlowController = new FlowController(); [UANode(BrowsePath = "/LCX001")] public LevelController LevelController = new LevelController(); [UANode(BrowsePath = "/CCX001")] public CustomController CustomController = new CustomController(); } [UAType] class BoilerInputPipe { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/FTX001")] public FlowTransmitter FlowTransmitter1 = new FlowTransmitter(); [UANode(BrowsePath = "/ValveX001")] public Valve Valve = new Valve(); } [UAType] class BoilerDrum { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/LIX001")] public LevelIndicator LevelIndicator = new LevelIndicator(); } [UAType] class BoilerOutputPipe { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [UANode(BrowsePath = "/FTX002")] public FlowTransmitter FlowTransmitter2 = new FlowTransmitter(); } [UAType] class FlowController : GenericController { } [UAType] class LevelController : GenericController { } [UAType] class CustomController { [UANode, UAData(Operations = UADataMappingOperations.Write)] // not readable public double Input1 { get; set; } [UANode, UAData(Operations = UADataMappingOperations.Write)] // not readable public double Input2 { get; set; } [UANode, UAData(Operations = UADataMappingOperations.Write)] // not readable public double Input3 { get; set; } [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double ControlOut { get; set; } [UANode, UAData] public string Description { get; set; } } [UAType] class FlowTransmitter : GenericSensor { } [UAType] class Valve : GenericActuator { } [UAType] class LevelIndicator : GenericSensor { } [UAType] class GenericController { [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double Measurement { get; set; } [UANode, UAData] public double SetPoint { get; set; } [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double ControlOut { get; set; } } [UAType] class GenericSensor { // Meta-members are filled in by information collected during mapping, and allow access to it later from your code. // Alternatively, you can derive your class from UAMappedNode, which will bring in many meta-members automatically. [MetaMember("NodeDescriptor")] public UANodeDescriptor NodeDescriptor { get; set; } [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing public double Output { get => _output; set { _output = value; Console.WriteLine($"Sensor \"{NodeDescriptor}\" output is now {value}."); } } private double _output; } [UAType] class GenericActuator { [UANode, UAData(Operations = UADataMappingOperations.Write)] // generic actuator input is not readable public double Input { get; set; } } }
' ' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . ' OPC client and subscriber examples in VB.NET on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-VBNET . ' Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own ' a commercial license in order to use Online Forums, and we reply to every post. Imports OpcLabs.BaseLib.LiveMapping Imports OpcLabs.EasyOpc.UA Imports OpcLabs.EasyOpc.UA.LiveMapping ' The Boiler and its constituents are described in our application domain terms, the way we want to work with them. ' Attributes are used to describe the correspondence between our types and members, and OPC nodes. ' This is how the boiler looks in OPC address space: ' - Boiler #1 ' - CC1001 (CustomController) ' - ControlOut ' - Description ' - Input1 ' - Input2 ' - Input3 ' - Drum1001 (BoilerDrum) ' - LIX001 (LevelIndicator) ' - Output ' - FC1001 (FlowController) ' - ControlOut ' - Measurement ' - SetPoint ' - LC1001 (LevelController) ' - ControlOut ' - Measurement ' - SetPoint ' - Pipe1001 (BoilerInputPipe) ' - FTX001 (FlowTransmitter) ' - Output ' - Pipe1002 (BoilerOutputPipe) ' - FTX002 (FlowTransmitter) ' - Output <UANamespace("http://opcfoundation.org/UA/Boiler/"), UAType()> _ Friend Class Boiler ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/PipeX001")> _ Public InputPipe As New BoilerInputPipe() <UANode(BrowsePath:="/DrumX001")> _ Public Drum As New BoilerDrum() <UANode(BrowsePath:="/PipeX002")> _ Public OutputPipe As New BoilerOutputPipe() <UANode(BrowsePath:="/FCX001")> _ Public FlowController As New FlowController() <UANode(BrowsePath:="/LCX001")> _ Public LevelController As New LevelController() <UANode(BrowsePath:="/CCX001")> _ Public CustomController As New CustomController() End Class <UAType()> _ Friend Class BoilerInputPipe ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/FTX001")> _ Public FlowTransmitter1 As New FlowTransmitter() <UANode(BrowsePath:="/ValveX001")> _ Public Valve As New Valve() End Class <UAType()> _ Friend Class BoilerDrum ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/LIX001")> _ Public LevelIndicator As New LevelIndicator() End Class <UAType()> _ Friend Class BoilerOutputPipe ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <UANode(BrowsePath:="/FTX002")> _ Public FlowTransmitter2 As New FlowTransmitter() End Class <UAType()> _ Friend Class FlowController Inherits GenericController End Class <UAType()> _ Friend Class LevelController Inherits GenericController End Class <UAType()> _ Friend Class CustomController <UANode(), UAData(Operations:=UADataMappingOperations.Write)> Public Property Input1 As Double <UANode(), UAData(Operations:=UADataMappingOperations.Write)> Public Property Input2 As Double <UANode(), UAData(Operations:=UADataMappingOperations.Write)> Public Property Input3 As Double <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)> Public Property ControlOut As Double <UANode(), UAData()> Public Property Description As String End Class <UAType()> _ Friend Class FlowTransmitter Inherits GenericSensor End Class <UAType()> _ Friend Class Valve Inherits GenericActuator End Class <UAType()> _ Friend Class LevelIndicator Inherits GenericSensor End Class <UAType()> _ Friend Class GenericController <UANode(), UAData(Operations := UADataMappingOperations.ReadAndSubscribe)> Public Property Measurement As Double <UANode(), UAData()> Public Property SetPoint As Double <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)> Public Property ControlOut As Double End Class <UAType()> _ Friend Class GenericSensor ' Meta-members are filled in by information collected during mapping, and allow access to it later from your code. ' Alternatively, you can derive your class from UAMappedNode, which will bring in many meta-members automatically. <MetaMember("NodeDescriptor")> Public Property NodeDescriptor As UANodeDescriptor <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)> Public Property Output() As Double ' no OPC writing Get Return _output End Get Set(ByVal value As Double) _output = value Console.WriteLine("Sensor ""{0}"" output is now {1}.", NodeDescriptor, value) End Set End Property Private _output As Double End Class <UAType()> _ Friend Class GenericActuator <UANode(), UAData(Operations := UADataMappingOperations.Write)> Public Property Input As Double End Class
Copyright © 2004-2024 CODE Consulting and Development, s.r.o., Plzen. All rights reserved. Web page: www.opclabs.com
Documentation Home, Send Feedback. Resources: Knowledge Base, Product Downloads. Technical support: Online Forums, FAQ.Missing some example? Ask us for it on our Online Forums! You do not have to own a commercial license in order to use Online Forums, and we reply to every post.